﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Collections;

using King.Extensions;
using King.Text;

namespace King.Automation {

    internal sealed class CommandHelpHelper : TextNode {

        internal const int LineLength = 75;
        internal const int MinWidth = 10;

        internal static IEnumerable<object> Header(CommandAssembly commandAssembly) {
            yield return new TextSingleSpaced() {
                new TextJoin() {
                    commandAssembly.Name, "-", 
                    commandAssembly.Description + ",", 
                    "Version", commandAssembly.Version,
                }, 
                
                commandAssembly.Copyright,
            };
        }
        internal static IEnumerable<object> ParameterDocumentation(CommandParameterInfo parameter) {
            yield return parameter.Documentation;

            if (parameter.IsRelativeToCurrentDirectory)
                yield return "Path is relative to current directory.";

            if (parameter.IsExistingFile)
                yield return "File must already exist.";
            if (parameter.IsExistingDirectory)
                yield return "Directory must already exist.";
            if (parameter.IsExistingDirectoryOrFile)
                yield return "Directory or file must already exist.";

            if (parameter.Type.IsEnum)
                yield return new TextNodes() { "Valid values are ", new TextJoin(", ", lastDelimiter: ", or ") { 
                    Enum.GetNames(parameter.Type.ElementTypeOrSelf.RuntimeType)
                }, "." };

            if (parameter.Type.IsEnumeration)
                yield return "Use ';' to separate multipule values.";

            if (parameter.DefaultValue != null) {
                var defaultValue = parameter.DefaultValue;
                if (parameter.Type.IsString)
                    defaultValue = new TextNodes() { "\"", defaultValue, "\"" };
                yield return new TextNodes() { "Default value is ", defaultValue, "." };
            }
        }
        internal static IEnumerable<object> ParametersDocumentation(CommandInfo commandInfo) {
            var parameters = commandInfo.Parameters();
            var maxWidth = new[] { MinWidth, parameters.Max(o => o.Name.Length) }.Max();

            return new TextSingleSpaced() {
                "Parameters:",

                new TextDoubleSpaced() {
                    from o in parameters
                    let name = (o.IsSwitch ? "/" : null) + o.Name.ToLowerFirst()
                    select new CommandSummary(
                        wrapAfter: " /\\",
                        lineLength: LineLength,
                        headerWidth: maxWidth + 3,
                        header: name) {

                        new TextJoin() { ParameterDocumentation(o) }
                    }
                }
            };
        }
        internal static IEnumerable<object> CommandHeader(CommandInfo commandInfo) {
            yield return new TextWrap(LineLength) { 
                new TextJoin() { commandInfo.Documentation } 
            };
        }
        internal static IEnumerable<object> CommandDocumentation(CommandInfo commandInfo) {
            yield return new CommandSummary(
                lineLength: LineLength,
                header: new TextJoin() { commandInfo.Assembly.Name, commandInfo.Name.ToLowerFirst() }) {
                new TextJoin() {
                    commandInfo.Verb == null ? null : "/" + commandInfo.Verb.ToLowerFirst(),

                    from o in commandInfo.Parameters()
                    let name = (o.IsSwitch ? "/" : null) + o.Name.ToLowerFirst()
                    select o.IsRequired ? (object)name : new TextNodes() { "[", name, "]" }
                }
            };
        }
    }

    [Command]
    public sealed class HelpCommands : Command {

        private CommandAssembly m_assembly;

        public HelpCommands(CommandAssembly assembly) {
            m_assembly = assembly;
        }

        protected override IEnumerable<object> Execute(TextNodeContext context) {
            var nl = Environment.NewLine;

            yield return CommandHelpHelper.Header(m_assembly);
            yield return nl;

            yield return nl;
            yield return "Type " + m_assembly.Name + " <command name> /? for command line description." + nl;
            yield return "Type " + m_assembly.Name + " <command name> /?? for command line and parameter description." + nl;

            if (m_assembly.Commands().None())
                yield break;

            var maxWidth = m_assembly.Commands().Max(o => o.Name.Length);

            yield return nl;

            var width = new[] { maxWidth + 2, CommandHelpHelper.MinWidth }.Max();
            yield return new TextDoubleSpaced(prefix: "Commands:" + nl) {
                from o in m_assembly.Commands()
                let name = new ConsoleMarkup(foreground: ConsoleColor.White) { o.Name.ToLowerFirst() }
                select new CommandSummary(
                    header: name, 
                    headerWidth: width, 
                    lineLength: CommandHelpHelper.LineLength) {

                    o.Documentation
                }
            };
        }
    }

    [Command]
    public sealed class HelpDetailedCommand : Command {

        public const string DetailedHelpSwitch = "/??";

        private CommandInfo m_command;
        private CommandMethodInfo verb;

        public HelpDetailedCommand(CommandInfo command) {
            Contract.Requires(command != null);
            Contract.Requires(!command.IsAbstract);

            m_command = command;
        }
        public HelpDetailedCommand(CommandMethodInfo verb) {
        }

        protected override IEnumerable<object> Execute(TextNodeContext context) {
            return new TextDoubleSpaced() {
                CommandHelpHelper.Header(m_command.Assembly),
                CommandHelpHelper.CommandHeader(m_command),
                CommandHelpHelper.CommandDocumentation(m_command),
                CommandHelpHelper.ParametersDocumentation(m_command)
            };
        }
    }

    [Command]
    public sealed class HelpCommand : Command {

        public const string HelpSwitch = "/?";

        private CommandInfo m_command;
        private CommandMethodInfo verb;

        public HelpCommand(CommandInfo command) {
            Contract.Requires(command != null);

            m_command = command;
        }
        public HelpCommand(CommandMethodInfo verb) {
        }

        protected override IEnumerable<object> Execute(TextNodeContext context) {
            return new TextDoubleSpaced() {
                CommandHelpHelper.Header(m_command.Assembly),

                CommandHelpHelper.CommandHeader(m_command),

                !m_command.IsAbstract ?
                    CommandHelpHelper.CommandDocumentation(m_command) :
                    m_command.Verbs().Select(o => CommandHelpHelper.CommandDocumentation(o))
            };
        }
    }

    public sealed class CommandAssemblyName : TextNode {

        private CommandAssembly m_commandAssembly;

        public CommandAssemblyName(CommandAssembly commandAssembly) {
            Contract.Requires(commandAssembly != null);

            m_commandAssembly = commandAssembly;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            yield return m_commandAssembly.Name;
        }

        public CommandAssembly CommandAssembly {
            get { return m_commandAssembly; }
        }
    }
    public sealed class CommandName : TextNode {

        private CommandInfo m_commandInfo;

        public CommandName(CommandInfo commandInfo) {
            Contract.Requires(commandInfo != null);

            m_commandInfo = commandInfo;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            yield return m_commandInfo.Name;
        }

        public CommandInfo CommandType {
            get { return m_commandInfo; }
        }
    }

    public sealed class CommandSummary : TextNodes {

        private object m_header;
        private int? m_headerWidth;
        private int? m_lineLength;
        private string m_wrapAfter;

        public CommandSummary(object header, 
            int? headerWidth = null, 
            int? lineLength = null,
            string wrapAfter = " ") {
            Contract.Requires(header != null);
            Contract.Requires(headerWidth == null || headerWidth > 0);
            Contract.Requires(lineLength == null || lineLength > 0);

            m_headerWidth = headerWidth;
            m_lineLength = lineLength;
            m_header = header;
            m_wrapAfter = wrapAfter;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            var header = context.Evaluate(m_header);

            yield return m_header;

            var width = m_headerWidth;
            if (width == null || header.Length >= width) 
                width = header.Length + 1;
                
            yield return "".PadLeft((int)width - header.Length);

            var children = Children();
            if (m_lineLength != null) {
                var indent = context.Evaluate(TextStartLine.Singleton);
                children = new TextWrap(
                    wrapAfter: m_wrapAfter,
                    lineLength: (int)m_lineLength, 
                    startColumn: (int)width + indent.Length) { children };
            }

            yield return new TextIndent("".PadLeft((int)width)) { children };
        }
    }
}